#include "gwbase/base/conf.h"

#include "gwbase/base/util.h"
#include "gwbase/base/logging.h"

namespace dicom {

namespace {

const int64_t kGiga = (1024L * 1024L * 1024L);
const int64_t kMega = (1024L * 1024L);
const int64_t kKilo = (1024L);

enum OneLineResult {
  kEmptyOrCommet = 0,
  kSucces,
  kSection,
  kError
};

OneLineResult ParseOneLine(Slice line, Slice* key, Slice* value) {
  enum {
    st_start = 0,
    st_in_section,
    st_in_key,
    st_expect_eq,
    st_expect_value,
    st_in_value,
    st_done
  } state;

  state = st_start;
  const char* pos = line.begin();
  const char* end = line.begin() + line.size();

  const char* key_start;
  const char* val_start;

  for (; pos != end; ++pos) {
    char c = *pos;
    assert(c != '\n');
    bool blank = ::isspace(c);
    switch (state) {
      case st_start:
        if (blank) {
          break;
        } else if (c == '#' || c == ';') {
          goto end;
        } else if (c == '=') {
          return kError;
        } else if (c == '[') {
          key_start = pos + 1;
          state = st_in_section;
        } else {
          key_start = pos;
          state = st_in_key;
        }
        break;
      case st_in_section:
        if (c == ']') {
          *key = Slice(key_start, pos - key_start);
          return kSection;
        } else if (blank) {
          return kError;
        }
      case st_in_key:
        if (blank) {
          *key = Slice(key_start, pos - key_start);
          state = st_expect_eq;
          break;
        } else if (c == '=') {
          *key = Slice(key_start, pos - key_start);
          state = st_expect_value;
          LOG_TRACE << "found key: " << key->ToString();
          break;
        } else {
          // in key
          break;
        }

      case st_expect_eq:
        if (blank) {
          break;
        } else if (c == '=') {
          state = st_expect_value;
          break;
        } else {
          LOG_TRACE << "expect eq, but got: " << c;
          goto end;
        }

      case st_expect_value:
        if (!blank) {
          val_start = pos;
          state = st_in_value;
        }
        break;

      case st_in_value:
        if (blank || c == ';' || c == '#') {
          *value = Slice(val_start, pos - val_start);
          state = st_done;
        }
        break;

      case st_done:
        goto end;

      default:
        assert(false);
        break;
    }
  }

  if (state == st_in_value) {
    LOG_TRACE << "in value";
    *value = Slice(val_start, end - val_start);
    state = st_done;
  }

end:
  if (state == st_start) {
    return kEmptyOrCommet;
  } else if (state == st_done) {
    return kSucces;
  } else {
    return kError;
  }
}

std::string MakeFullKey(const std::string& section, const std::string& key) {
  assert(!key.empty());
  return LowerString(section + "." + key);
}
}

Status Conf::ParseFile(const std::string& path) {
  assert(!valid_);
  Status s;
  std::string data;
  s = dicom::ReadSmallFile(path, &data);
  if (!s.ok()) return s;

  s = ParseString(data);
  return s;
}

Status Conf::ParseString(const std::string& data) {
  Status s;
  std::vector<Slice> lines;
  SplitToLines(data, &lines);

  std::string section;
  for (size_t i = 0; i < lines.size(); ++i) {
    Slice cur = lines[i];
    Slice key, value;
    OneLineResult result = ParseOneLine(cur, &key, &value);
    switch (result) {
      case kEmptyOrCommet:
        continue;
      case kSucces:
        if (key.empty()) {
          return Status(
              Status::kOther,
              StringPrintf("key is emtpy at line: %d: %s", static_cast<int>(i),
                           cur.ToString().c_str()));
        }
        LOG_TRACE << section << "." << key.ToString() << " = "
                  << value.ToString();
        kvs_[MakeFullKey(section, key.ToString())] = value.ToString();
        break;
      case kSection:
        section = key.ToString();
        break;
      case kError:
        return Status(
            Status::kOther,
            StringPrintf("failed to parse line: %d: %s", static_cast<int>(i),
                         cur.ToString().c_str()));
    }
  }

  return s;
}

int64_t Conf::GetInt64(const std::string& section, const std::string& key,
                       int64_t default_value) {
  KVMap::const_iterator it = kvs_.find(MakeFullKey(section, key));
  if (it == kvs_.end() || it->second.empty()) return default_value;

  char* end = NULL;
  return static_cast<int64_t>(::strtol(it->second.c_str(), &end, 0));
}

int64_t Conf::GetSpaceSpec(const std::string& section, const std::string& key,
                           int64_t default_value) {
  KVMap::const_iterator it = kvs_.find(MakeFullKey(section, key));
  if (it == kvs_.end() || it->second.empty()) return default_value;

  std::string s = it->second;
  int64_t v = 0;
  size_t i = 0;
  for (; i < s.size(); ++i) {
    if (::isdigit(s[i])) {
      v = v * 10 + (s[i] - '0');
    } else {
      break;
    }
  }

  // G/M/K
  if (i != s.size()) {
    char unit = s[i];
    switch (unit) {
      case 'G':
      case 'g':
        v *= kGiga;
        break;
      case 'M':
      case 'm':
        v *= kMega;
        break;
        break;
      case 'K':
      case 'k':
        v *= kKilo;
        break;
      default:
        break;
    }
  }

  return v;
}

int32_t Conf::GetInt32(const std::string& section, const std::string& key,
                       int32_t default_value) {
  KVMap::const_iterator it = kvs_.find(MakeFullKey(section, key));
  if (it == kvs_.end() || it->second.empty()) return default_value;

  char* end = NULL;
  return static_cast<int32_t>(::strtol(it->second.c_str(), &end, 0));
}

double Conf::GetDouble(const std::string& section, const std::string& key,
                       double default_value) {
  KVMap::const_iterator it = kvs_.find(MakeFullKey(section, key));
  if (it == kvs_.end() || it->second.empty()) return default_value;

  char* end = NULL;
  return ::strtod(it->second.c_str(), &end);
}

std::string Conf::GetString(const std::string& section, const std::string& key,
                            const std::string& default_value) {
  KVMap::const_iterator it = kvs_.find(MakeFullKey(section, key));
  if (it == kvs_.end() || it->second.empty()) return default_value;

  return it->second;
}

bool Conf::GetBool(const std::string& section, const std::string& key,
                   bool default_value) {
  KVMap::const_iterator it = kvs_.find(MakeFullKey(section, key));
  if (it == kvs_.end() || it->second.empty()) return default_value;

  std::string v = LowerString(it->second);
  if (v == "yes" || v == "on" || v == "true" || v == "y") {
    return true;
  } else if (v == "no" || v == "off" || v == "false" || v == "n") {
    return false;
  } else {
    return default_value;
  }
}

void Conf::AddInteger(const std::string& section, const std::string& key,
                      int value) {
  AddString(section, key, Fmt("%d", value).str());
}

void Conf::AddDouble(const std::string& section, const std::string& key,
                     double value) {
  AddString(section, key, Fmt("%.3lf", value).str());
}

void Conf::AddString(const std::string& section, const std::string& key,
                     const std::string& value) {
  kvs_[MakeFullKey(section, key)] = value;
}

void Conf::Dump(std::string* out) {
  KVMap::const_iterator it = kvs_.begin();
  for (; it != kvs_.end(); ++it) {
    out->append(it->first);
    out->append(" = ");
    out->append(it->second);
    out->append("\n");
  }
}
}
